import os
import shutil
import sys

from PyQt6.QtCore import QByteArray, QSettings, Qt, QSize
from PyQt6.QtGui import QFont, QFileSystemModel,  QIcon, QKeySequence, QTextDocument, QAction
from PyQt6.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog
from PyQt6.QtWidgets import (
    QApplication,
    QDialog,
    QDockWidget,
    QFileDialog,
    QMainWindow,
    QMenu,
    QMessageBox,
    QToolBar,
    QTreeView,
)

from .config import R
from .core.widgets.edit import Editor

CurrDir = os.path.dirname(os.path.abspath(__file__))
AppName = "x01.dide"


def get_runs(path):
    result = {}
    for root, dirs, files in os.walk(path):
        if os.path.dirname(root) == path:
            for name in files:
                if name == "run.py":
                    fullpath = os.path.join(root, name)
                    dirname = os.path.basename(root)
                    result[dirname] = fullpath
    return result


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.setWindowTitle("x01.dide")
        self.resize(900, 600)
        self.SetCenter()

        self.work_directory = "."
        self.filename = "."
        self.excel_filename = "."

        settings = QSettings("x01", AppName)
        if settings.value("work_directory"):
            self.work_directory = settings.value("work_directory")
        if settings.value("excel_filename"):
            self.excel_filename = settings.value("excel_filename")
        if settings.value("filename"):
            self.filename = settings.value("filename")
        if settings.value("MainWindow/Geometry"):
            self.restoreGeometry(QByteArray(settings.value("MainWindow/Geometry")))
        if settings.value("MainWindow/State"):
            self.restoreState(QByteArray(settings.value("MainWindow/State")))

        self.editor = Editor()
        self.setCentralWidget(self.editor)

        self.folderDockWidget = QDockWidget(AppName, self)
        self.folderDockWidget.setObjectName("FolderTree")
        self.folderModel: QFileSystemModel = QFileSystemModel()  # QDirModel()
        self.folderModel.setReadOnly(False)
        self.folderTreeView: QTreeView = QTreeView(self)
        self.folderTreeView.setModel(self.folderModel)
        index = self.folderModel.setRootPath(
            self.work_directory
        ) 
        self.folderTreeView.setRootIndex(index)
        self.folderTreeView.setHeaderHidden(True)
        for i in range(1, 4):
            self.folderTreeView.setColumnHidden(i, True)
        self.folderTreeView.doubleClicked.connect(self.OnFolderTreeClicked)
        self.folderDockWidget.setWindowTitle(os.path.basename(self.work_directory))

        self.folderTreeView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.folderTreeView.customContextMenuRequested.connect(self.ShowFolderMenu)
        self.InitFolderMenu()

        self.folderDockWidget.setWidget(self.folderTreeView)
        self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.folderDockWidget)
        self.folderDockWidget.setWindowTitle(os.path.basename(self.work_directory))
        self.folderDockWidget.show()

        self.statusBar().showMessage("Ready")

        self.InitActions()
        self.InitMenu()
        self.InitToolBar()

        self.InitRuns()

        helpMenu = self.menuBar().addMenu("&Help")
        self.AddActions(helpMenu, self.helpActions)

        self.printer = QPrinter(QPrinter.PrinterMode.HighResolution)

    def InitRuns(self):
        """
        CurrDir 需根据实际更改，约定为执行一级子目录下的 run.py
        """
        toolsMenu = self.menuBar().addMenu("&Runs")
        self.runs = get_runs(os.path.join(CurrDir, "runs"))
        for k in self.runs:
            a = QAction(k, self)
            a.triggered.connect(self.run_callback)
            toolsMenu.addAction(a)
        return

    def run_callback(self):
        runs = self.runs
        a: QAction = self.sender()
        key = a.text()
        if runs.get(key):
            os.system("python " + runs[key])  # python 需根据实际调整

    def InitFolderMenu(self):
        deleteFileAction = self.CreateAction("&Delete File or Folder", self.DeleteFile)
        addFileAction = self.CreateAction("&Add File", self.AddFile)
        addFolderAction = self.CreateAction("Add &Folder", self.AddFolder)
        self.folderActions = (deleteFileAction, addFileAction, addFolderAction)

        self.folder_menu = QMenu()
        self.AddActions(self.folder_menu, self.folderActions)

    def DeleteFile(self):
        index = self.folderTreeView.currentIndex()
        if self.folderModel.isDir(index):
            shutil.rmtree(self.folderModel.filePath(index))
        else:
            self.folderModel.remove(index)

    def AddFile(self):
        index = self.folderTreeView.currentIndex()
        path = self.folderModel.filePath(index)
        temp_path = os.path.join(os.path.dirname(path), "temp.py")
        with open(temp_path, "w") as f:
            pass
        index = self.folderModel.index(temp_path)
        self.folderTreeView.edit(index)

    def AddFolder(self):
        index = self.folderTreeView.currentIndex()
        index = self.folderModel.mkdir(index.parent(), "temp")
        self.folderTreeView.edit(index)

    def ShowFolderMenu(self, pos):
        self.folder_menu.exec(self.folderTreeView.mapToGlobal(pos))

    def InitActions(self):
        fileNewAction = self.CreateAction(
            "&New", self.FileNew, QKeySequence.StandardKey.New, "new", "Create a file"
        )
        fileOpenAction = self.CreateAction(
            "&Open", self.FileOpen, QKeySequence.StandardKey.Open, "load", "Open a exist file"
        )
        fileOpenFolderAction = self.CreateAction(
            "Open &Folder", self.FileOpenFolder, None, "folder", "Open a exist folder"
        )
        fileSaveAction = self.CreateAction(
            "&Save", self.FileSave, QKeySequence.StandardKey.Save, "save", "Save the file"
        )
        filePrintPreviewAction = self.CreateAction(
            "P&rint Preview", self.FilePrintPreview
        )
        fileMergeMarkdownAction = self.CreateAction(
            "&Merge Markdown File", self.FileMergeMarkdown
        )
        filePrintMarkDownAction = self.CreateAction(
            "&Print Markdown", self.FilePrintMarkdown, "Ctrl+P", "print"
        )
        fileQuitAction = self.CreateAction(
            "&Quit", self.close, "Ctrl+Q", "quit", "Close the application"
        )
        self.fileActions = (
            fileNewAction,
            fileOpenAction,
            fileSaveAction,
            None,
            fileOpenFolderAction,
            filePrintPreviewAction,
            fileMergeMarkdownAction,
            filePrintMarkDownAction,
            None,
            fileQuitAction,
        )

        zoomInAction = self.CreateAction(
            "&Zoom In", self.ZoomIn, "Ctrl++", "zoom-in", "Zoom In Text"
        )
        zoomOutAction = self.CreateAction(
            "Zomm &Out", self.ZoomOut, "Ctrl+-", "zoom-out", "Zoom Out Text"
        )
        self.editActions = (zoomInAction, zoomOutAction)

        terminalAction = self.CreateAction(
            "&Terminal", self.Terminal, "F6", icon="terminal"
        )
        runAction = self.CreateAction("&Run", self.Run, "F5", icon="run")
        checkAction = self.CreateAction("&Check code", self.Check, "F7", icon="check")
        self.debugActions = (terminalAction, runAction, checkAction)

        showFolderAction = self.CreateAction(
            "&Show Folder", self.ShowFolder, icon="folder"
        )
        self.viewActions = (showFolderAction,)

        aboutAction = self.CreateAction("&About", self.About, icon="about")
        helpAction = self.CreateAction("&Help", self.Help, icon="help")
        self.helpActions = (helpAction, aboutAction)

        self.toolActions = (
            fileNewAction,
            fileOpenAction,
            fileSaveAction,
            zoomInAction,
            zoomOutAction,
            None,
            terminalAction,
            runAction,
        )

    def InitMenu(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.AddActions(self.fileMenu, self.fileActions)

        editMenu = self.menuBar().addMenu("&Edit")
        self.AddActions(editMenu, self.editActions)

        debugMenu = self.menuBar().addMenu("&Debug")
        self.AddActions(debugMenu, self.debugActions)

        viewMenu = self.menuBar().addMenu("&View")
        self.AddActions(viewMenu, self.viewActions)

    def InitToolBar(self):
        toolBar = self.addToolBar("actions")
        toolBar.setIconSize(QSize(32,32))
        self.AddActions(toolBar, self.toolActions)

    def closeEvent(self, e):
        settings = QSettings("x01", AppName)
        settings.setValue("work_directory", self.work_directory)
        settings.setValue("excel_filename", self.excel_filename)
        settings.setValue("filename", self.filename)
        settings.setValue("MainWindow/Geometry", self.saveGeometry())
        settings.setValue("MainWindow/State", self.saveState())

    def SetCenter(self):
        g = self.screen().geometry()
        x = (g.width() - self.width()) // 2
        y = (g.height() - self.height()) // 2
        self.move(x,y // 4)

    def CreateAction(
        self,
        text,
        slot=None,
        shortcut=None,
        icon=None,
        tip=None,
        checkable=False,
        signal="triggered",
    ):
        action = QAction(text, self)
        actSignal = None
        if signal == "triggered":
            actSignal = action.triggered
        elif signal == "toggled":
            actSignal = action.toggled
        elif signal == "changed":
            actSignal = action.changed
        elif signal == "hovered":
            actSignal = action.hovered
        else:
            actSignal = action.triggered

        if icon is not None:
            iconpath = os.path.join(CurrDir, "images/" + icon + ".png")
            action.setIcon(QIcon(iconpath))
        else:
            action.icon = None

        if shortcut is not None:
            action.setShortcut(shortcut)
        if tip is not None:
            action.setToolTip(tip)
        if slot is not None:
            actSignal.connect(slot)
        if checkable:
            action.setCheckable(checkable)
        return action

    # edit actions
    def ZoomIn(self):
        self.editor.zoom_in()

    def ZoomOut(self):
        self.editor.zoom_out()

    # debug actions
    def Terminal(self):
        self.FileSave()
        ed = self.editor.tabs.currentWidget()
        if ed:
            path = os.path.dirname(ed.path)
            os.chdir(path)
        os.system(R.TERMINAL)

    def Run(self):
        self.FileSave()
        ed = self.editor.tabs.currentWidget()
        if ed:
            os.system(f"python {ed.path}")

    def Check(self):
        """需安装 pycodestyle, 在终端中显示"""
        self.FileSave()
        ed = self.editor.tabs.currentWidget()
        if ed:
            os.system(f"python -m pycodestyle {ed.path}")

    # help actions
    def Help(self):
        QMessageBox.about(
            self,
            AppName,
            f'<p>帮助文档链接: <a href="https://gitee.com/chinax01/x01.dide/blob/master/node.md">{AppName}</a></p>',
        )

    def About(self):
        QMessageBox.about(
            self,
            AppName,
            f"一个简易的 Python IDE， 版本 {R.Version}\nCopyright 2021 by x01. All rights reserved.\n版权属于黄雄(x01)，所有权利保留。",
        )

    # View
    def ShowFolder(self):
        self.folderModel.refresh()
        self.folderDockWidget.show()

    # File
    def FileNew(self):
        self.editor.filename = self.filename
        self.editor.new()
        self.filename = self.editor.filename

    def FileOpen(self):
        self.editor.filename = self.filename
        self.editor.open()
        self.filename = self.editor.filename

    def FileOpenFolder(self):
        currdir = CurrDir if self.filename is None else os.path.dirname(self.filename)
        currdir = QFileDialog.getExistingDirectory(directory=currdir)
        name = os.path.basename(currdir)
        self.folderDockWidget.setWindowTitle(name)
        index = self.folderModel.index(currdir)
        self.folderTreeView.setRootIndex(index)
        self.work_directory = currdir

    def OnFolderTreeClicked(self, e):
        index = self.folderTreeView.currentIndex()
        path = self.folderModel.filePath(index)
        if os.path.isdir(path):
            if self.folderTreeView.isExpanded(index):
                self.folderTreeView.collapse(index)
            else:
                self.folderTreeView.expand(index)
            return

        # fmt: off
        if not path.endswith(
            (".py", ".cs", ".md", ".h", ".c", ".cpp", ".txt", ".ui", ".js",
             ".html", ".htm", ".css", ".java", ".sh", ".xml", ".json", ".cfg",
            )    # 非文本格式打开时会崩溃
        ):
            return
        # fmt: on

        with open(path, encoding=R.Encoding) as f:
            text = f.read()
        self.editor.add_tab(path, text)
        self.filename = path

    def FileSave(self):
        self.editor.save()

    # 合并打印 md 可生成 pdf 文件
    def FileMergeMarkdown(self):
        currdir = CurrDir if self.filename is None else os.path.dirname(self.filename)
        currdir = QFileDialog.getExistingDirectory(directory=currdir)
        self.MergeFiles(curr_dir=currdir)
        QMessageBox.about(
            self,
            "Merge md",
            'Merger markdown files success! output filename is "tmp.md".',
        )

    def GetFiles(self, curr_dir, ext):
        filenames = []
        dir = os.path.abspath(curr_dir)
        files = os.listdir(dir)
        for f in files:
            path = os.path.join(dir, f)
            if os.path.isdir(path):
                self.GetFiles(path, ext)
                continue
            extname = os.path.splitext(f)[1]
            if ext == extname:
                filenames.append(os.path.join(dir, f))
        return filenames

    def MergeFiles(self, curr_dir=".", ext=".md", outname="tmp.md", sort_func=None):
        filenames = self.GetFiles(curr_dir, ext)
        if sort_func is not None:
            filenames = sort_func(filenames)
        writer = open(os.path.join(curr_dir, outname), "ab")
        for filename in filenames:
            with open(filename, "rb") as f:
                writer.write(f.read())
        writer.close()

    def FilePrintPreview(self):
        dlg = QPrintPreviewDialog(self)
        self.FilePrint(dlg)

    def FilePrint(self, dlg):
        ed = self.editor.tabs.currentWidget()
        if ed:
            text = ed.text()
            print(text.split("\n")[0])
        document = QTextDocument()
        document.setMarkdown(text)

        if isinstance(dlg, QPrintPreviewDialog):
            preview = dlg
            preview.setWindowFlags(
                preview.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint
            )
            preview.paintRequested.connect(document.print)
            preview.exec()
        elif isinstance(dlg, QPrintDialog):
            if dlg.exec() == QDialog.Accepted:
                document.print(self.printer)

    def FilePrintMarkdown(self):
        dlg = QPrintDialog(self.printer, self)
        dlg.setWindowTitle(self.tr("Print Document"))
        self.FilePrint(dlg)

    def AddActions(self, target, actions):
        for action in actions:
            if action is None:
                if not isinstance(target, QToolBar):
                    target.addSeparator()
            elif isinstance(target, QToolBar) and action.icon is None:
                continue
            else:
                target.addAction(action)


def run():
    app = QApplication(sys.argv)
    font = QFont()
    font.setPointSize(14)
    app.setFont(font)
    win = MainWindow()
    win.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    run()
